李守中

Linux SWAP 相关

Table of Contents

1. swap 概念

Swap 分区 也称交换分区,它可以是硬盘上的一个分区,也可以是一个指定的 Swap File

当内存不足时,内存中长时间不用的数据会被写到 Swap 并从内存删除,从而空出内存给其他程序使用。

当然,这也意味着,CPU 从 Swap 中,即硬盘中,读写数据的速度比从内存中的速度慢得多。

2. 创建 swap

Swap 分区可以使用 fdisk 或者其他工具进行创建,但我更倾向于使用 Swap 文件。

使用 sudo fallocate -l 8G /swapfile 在根目录下创建一个名为 swapfile 大小 8G 的文件。

非 Debian 系系统,比如 OpenSuSE 用 sudo dd if=/dev/zero of=/swapfile count=8192 bs=1MiB 有相同效果。

sudo chmod 600 /swapfile 修改 /swapfile 的权限,只有 root 用户才可读写。

sudo mkswap /swapfile 格式化 swap 文件。

sudo swapon /swapfile 激活 swap。

sudo swapon --show 查看是否开启 swap,结果类似下面这样:

NAME      TYPE SIZE USED PRIO
/swapfile file   8G   0B   -2

把 /swapfile 添加到 /etc/fstab 使系统自动挂载 swap:

/swapfile swap swap defaults 0 0

或者直接使用以下命令:

sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab

在删除一个 swap file 前,必须先停用它。

执行 sudo swapoff <name> 停用指定的 swap,其中 <name> 可在 sudo swapon --show 中查询。

从 /etc/fstab 中删除相关条目,最后 rm -rf /swapfile 删除文件。

3. 多个 swap 的优先级

如果有多个 swap 文件或者分区,可以给它们各自优先级值,范围是 0-32767 。数字越大,越优先被使用。

优先级可以在 /etc/fstab 中通过 pri 参数指定:

/dev/sda1 none swap defaults,pri=100 0 0
/dev/sdb2 none swap defaults,pri=10  0 0

或者通过 swapon 的 −p−−priority 参数指定:

sudo swapon -p 100 /dev/sda1

如果两个或以上的 swap 有同样的优先级,并且它们都是当前可用的最高优先级,page 会按照循环的方式在它们之间分配。

4. 内核调参优化

4.1. 内存回收机制

内存回收操作针对的 page 由定义在 mm/vmscan.c 内一个 enum 中的链表组成。主要有 4 个:

  • anon - inactive
  • anon - active
  • file - inactive
  • file - active

就是说,内存回收操作主要针对的就是内存中的文件页 file cache 和匿名页 anon

关于内存页活跃 active 与不活跃 inactive 的判断由内核使用 LRU 算法进行处理并标记,不详细展开这个过程。

整个扫描的过程分几个循环:

  1. 首先扫描每个 zone 上的 cgroup 组;
  2. 然后以 cgroup 的内存为单元进行 page 链表的扫描:
    1. 先扫描 anon 的 active 链表,将不频繁的放进 inactive 链表中;
    2. 扫描 inactive 链表,将里面活跃的移回 active 中;
    3. 按照同样的顺序扫描 file cache 对应的 page 链表。

进行 swap 操作时,先换出 inactive page。

如果 page 是 file cache,则判断其是否为脏数据,如果是脏数据就写回,不是脏数据可以直接释放。

这样看来,内存回收这个行为会对两种内存的使用进行回收:

  1. anon 匿名页内存,主要回收手段是 swap;
  2. file-backed 文件映射页,主要的释放手段是写回和清空。

因为针对 filebased 的内存,没必要交换,其数据原本就在硬盘上,回收这部分内存只要在有脏数据时写回,并清空内存就好,以后有需要再从对应的文件读回来。

内存对匿名页和文件缓存一共用了四条链表进行组织,回收过程主要是针对这四条链表进行扫描和操作。

4.2. 内核参数 swappiness

sysctl 的参数中有一个叫 swappiness ,取值范围 0-100 ,默认 60

cat /proc/sys/vm/swappiness
60

注意,这个 60 不是百分比,只用来定义内核释放内存的倾向:

  • 值越高,内核就会越积极的使用 swap;
  • 值越低,就会降低对 swap 的使用积极性。
  • 如果这个值为 0,那么内存在 free 和 file-backed 使用的页面总量小于高水位标记 ( high water mark ) 之前,不会发生交换。

在这里可以理解 file-backed page 这个词的含义了,实际上就是上文所说的文件映射页的数据。

如果回收内存可以有两种途径:

  1. anon 匿名缓存,进入 swap。
  2. file 文件缓存,回写。

那么内存回收的时候要平衡两种回收手段的使用,以达到最优。除此之外,在处理匿名缓存时,如果符合交换条件的内存较长,比如可以交换的内存有 100M,但是目前只需要 50M,实际交换 50M 就好,不用交换所有的数据。

Linux 内核中处理上述逻辑的代码写在 get_sacn_count() ( 这个方法被 shrink_lruvec() 调用 ) 中,swappiness 是它所需要的一个参数。这个参数影响内核在清空内存的时,对于 file-backed 回写与匿名缓存进入 swap 的倾向。

get_sacn_count() 函数的处理部分代码,关于 swappiness 的注释的很清楚: 如果 swappiness 设为 100,那么匿名缓存和文件缓存将用同样的优先级进行回收。

很明显,使用清空文件的方式将有利于减轻内存回收时可能造成的 I/O 压力。因为如果 file-backed 中的数据不是脏数据的话,可以不用写回,直接丢弃,这样就没有 I/O 发生,若进行交换,就一定会由 I/O 操作。

所以系统默认将 swappiness 的值设置为 60,这样回收内存时,对 file-backed 内存的清空比例会更大,内核将会更倾向于进行缓存清空而不是交换。

这里的 swappiness 取值 60 不是说内核回收的时会按照 60:40 的比例去做相应的 swap 和清空 file-backed。在做这个比例计算的时候,内核还要参考当前内存使用的其他信息。get_sacn_count() 函数中有详细的实现算法。

要明确的概念是: swappiness 的值是用来控制内存回收时,回收的匿名页更多一些还是回收的文件缓存更多一些。

swappiness 设置为 0 时,如果 file-backed 数据清理完成而内存还是不够用的话,内核依旧会进行 swap 操作。

除此之外,还有一种情况会导致内核直接使用 swap: 触发全局回收,并且 zonefile + zonefree <= high_wmark_pages(zone) 条件成立时,就将 scan_balance 这个标记置为 SCAN_ANON 。后续处理 scan_balance 时,内核检测到它的值是 SCAN_ANON ,则一定会进行针对匿名页的 swap 操作。

要理解这个行为,首先要搞清楚什么是高水位标记 ( high_wmark_pages )。

Linux 内核使用 水位标记 watermark 来描述内存压力情况,由 3 种标记 high low min 构成。

标记的含义分别为:

  • 内存剩余在 high 以上表示内存剩余较多,目前内存压力不大。
  • 内存剩余在 high 到 low 之间表示目前内存有一定压力。
  • 内存剩余在 low 以下表示内存开始有较大使用压力。

而小于 min 的这部分内存,内核是保留给特定情况下使用的,一般不会分配。

内存回收行为基于 watermark 进行决策:

  • 当剩余内存低于 watermark[low] 时,内核的 kswapd 开始回收内存。直到剩余内存达到 watermark[high] 后停止。
  • 当剩余内存低于 watermark[min] 时,就会触发直接回收 direct reclaim

明白了水位标记的概念之后, zonefile + zonefree <= high_wmark_pages(zone) 这个公式就能理解了:

  • zonefile 相当于内存中文件映射的总量。
  • zonefree 相当于剩余内存的总量。

内核一般认为,如果 zonefile 还有的话,就可以尽量通过清空文件缓存获得部分内存,不必只用 swap 方式处理 anon 内存数据。

就是说在全局回收的状态下 ( 有 global_reclaim(sc) 标记 ),如果当前的 文件映射内存总量 + 剩余内存总量 <= watermark[high] 时直接使用 swap。

这个判断对系统的影响是, swappiness 设置为 0 时,即使有剩余内存,也可能出现 swap 操作

watermark 是根据 内存总大小/proc/sys/vm/min_free_kbytes 参数进行运算得来的,这个参数是:

这个参数本身决定了系统中每个 zone 的 watermark[min] 的值大小。然后内核根据 min 的大小并参考每个 zone 的内存大小分别算出每个 zone 的 low 水位和 high 水位值。

具体逻辑可以参见源代码目录下的 mm/page_alloc.c。

可以从 /proc/zoneinfo 文件中查看当前系统的相关的信息和使用情况。

/etc/sysctl.d/90-mysettings.conf
vm.swappiness=20

4.3. 内核参数 vfs_cache_pressure

参数 vm.vfs_cache_pressure 表示内核回收 directory 和 inode cache 内存数据的 倾向:

  • 默认值 100 表示内核根据 pagecache ( 内存 ) 和 swapcache ( swap ),把 directory 和 inode cache 保持在合理的百分比;
  • 低于 100 内核倾向于 保留 directory 和 inode cache;
  • 高于 100 内核倾向于 回收 directory 和 inode cache。
/etc/sysctl.d/90-mysettings.conf
vm.vfs_cache_pressure=50

5. 内核功能 zswap

频繁使用 swap 会降低内存性能。要避免内存性能降低有两个思路,一个是提高读写磁盘的速度,比如将机械硬盘换成固态盘,一个是降低使用 swap 的频率。

Linux kernel 3.11 引入了 zswap 功能。

zswap 单独使用一部份 RAM 作为存储池,将原本要被交换的 RAM 数据进行压缩,放入存储池中,一旦池满或 RAM 耗尽,最近最少使用 ( LRU ) 的页就会被解压缩并写入磁盘,就像 zswap 不存在时一样。将池中的页解压缩到 swap 后,会释放池中的压缩版本。

这个思路可以延缓发生 swap 的进度。

sudo echo 1 | tee /sys/module/zswap/parameters/enabled 可以临时启用 zswap。

若要 zswap 随内核启动,则需要修改内核的启动参数:

编辑 /etc/default/grub 配置文件。在 GRUB_CMDLINE_LINUX_DEFAULT 中加上:

GRUB_CMDLINE_LINUX="..."
GRUB_CMDLINE_LINUX_DEFAULT="... zswap.enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=20 zswap.zpool=z3fold"

其中:

  1. zwap.enabled 表示是否启用 zswap;
  2. zswap.compressor 表示用来压缩内存的算法,可以是 lzo lz4 lz4hc deflate 任选其一;
  3. zswap.max_pool_percent 用来表示内存池的上限,内存池根据需要缓慢增加,但最大不超过限定值;
  4. zswap.zpool 内存池的管理算法,可以是 zbudz3fold ,其中 z3fold 比 zbud 有更好的压缩率,也需要更多的 CPU 资源;

在 Debian 系发行版上执行 sudo update-grub 或者 sudo grub-mkconfig -o /boot/grub/grub.cfg 重新生成 GRUB 配置文件后,重启系统。

在 RedHat 系发行版上执行 sudo grub2-mkconfig -o /boot/grub2/grub.cfg 重新生成 GRUB 配置文件后,重启系统。

重启后,执行 dmesg | grep zswap 确认 zswap 是否正常开启。

在 archlinux 上,还可以通过 systemd-swap ( 这包是 AUR 源里的,Debian 和 Ubuntu 默认没装这个野包 ) 开启 zswap:

注意: 并不推荐使用这个软件。

sudo pacman -S systemd-swap --noconfirm
sudo systemctl enable systemd-swap
sudo systemctl restart systemd-swap

若想要通过 systemd-swap 管理 zswap 可以通过 /etc/systemd/swap.conf 进行配置:

$ grep -i zswap /etc/systemd/swap.conf
# Zswap
# Zswap create compress cache between swap and memory for reduce IO
# https://www.kernel.org/doc/Documentation/vm/zswap.txt
zswap_enabled=1
zswap_compressor=lz4      # lzo lz4
zswap_max_pool_percent=25 # 1-99
zswap_zpool=zbud          # zbud z3fold

6. 内核功能 zram

zram 与 zswap 类似,都要在内存中单独开辟一块空间。区别在于,zram 使用内存模拟 swap 设备,不会把 swap 中的数据放入磁盘中。

zram 在内存中单独开辟一块空间并将其模拟成为磁盘设备,当 swap 行为产生时,数据被压缩后放在 zram 模拟的磁盘设备中 ( 还是在内存里 )。

由于在内存中压缩和解压缩的速度远比在磁盘中快,因此 zram 在移动终端设备广泛被应用。zram 是基于 ram 的 block device,默认的 swap priority 会很靠前。只有 zram 空间满了,系统才会考虑其他的 swap 设备。当然这个优先级可以自行配置。

注意: zram + swap 的性能可能差于 zswap,zram 只在没有 swap 的情况下很好用。

6.1. 开启 zram

  1. 配置内存压缩算法。比如,使用 lz4 压缩算法: echo lz4 > /sys/block/zram0/comp_algorithm
  2. 配置 zram 大小。比如,配置 zram 大小为 2GB,有两种写法:
    • echo 2G > /sys/block/zram0/disksize
    • echo $((2*1024*1024*1024)) > /sys/block/zram0/disksize
  3. 开启 zram: mkswap /dev/zram0 &&​ swapon /dev/zram0
  4. zram 设备个数设定。如果把 zram 作为为内核模块加载,那么可以在内核模块加载的时候,添加参数: insmod zram.ko num_devices=4

6.2. zram 优化

压缩流是内核 v3.14 新加入的功能。

v3.14 之前的 zram 仅使用一个压缩流,每个写 ( 压缩 ) 操作独享各自的压缩流 ( 主要是独享缓存,还能给流指定压缩算法 )。

单压缩流的情况下,如果写 ( 压缩 ) 操作出现卡顿或崩溃,所有的写 ( 压缩 ) 操作将一直处于等待状态;而多压缩流的架构会让写 ( 压缩 ) 操作并行执行,可以提高压缩效率和稳定性。

cat /sys/block/zram0/max_comp_streams 可以查看压缩流数量,默认数量是 1。

可以 echo 3 > /sys/block/zram0/max_comp_streams 直接向 proc 文件写入要使用几个压缩流。

/sys/block/zram0 路径下的文件的其他参数:

Name Access Description
disksize RW 显示和设置该块设备的内存大小
initstate RO 显示设备的初始化状态
reset WO 重置设备
num_reads RO 读数据的个数
failed_reads RO 读数据失败的个数
num_write RO 写数据的个数
failed_writes RO 写数据失败的个数
invalid_io RO 非页面大小对齐的I/O请求的个数
max_comp_streams RW 最大可能同时执行压缩操作的个数
comp_algorithm RW 显示和设置压缩算法
notify_free RO 空闲内存的通知个数
zero_pages RO 写入该块设备的全为的页面的个数
orig_data_size RO 保存在该块设备中没有被压缩的数据的大小
compr_data_size RO 保存在该块设备中已被压缩的数据的大小
mem_used_total RO 分配给该块设备的总内存大小
mem_used_max RW 该块设备已用的内存大小,可以写 1 重置这个计数参数到当前真实的统计值
mem_limit RW zram 可以用来保存压缩数据的最大内存
pages_compacted RO 在压缩过程中可用的空闲页面的个数
compact WO 触发内存压缩

6.3. 自动化配置 zram

RedHat 系发行版中的 zram-generator 可以提供 zram 的自动化管理功能。这个包最早出现在 Fedora 29 与 Debian 12 上,属于 systemd 的一个子项目。

详细配置可以看 man 页面或者 Github 的文档。



Last Update: 2023-08-13 Sun 14:27

Generated by: Emacs 28.2 (Org mode 9.5.5)   Contact: [email protected]

若正文中无特殊说明,本站内容遵循: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议